﻿using System;
using System.Diagnostics;
using System.Globalization;
using System.ServiceModel;
using System.Xml.Linq;
using Conducido.WcfMessageLogger.Services.Behaviors.Entities;
using Conducido.WcfMessageLogger.Services.Behaviors.Entities.Config;
using Conducido.WcfMessageLogger.Services.Behaviors.LogModel;
using Conducido.WcfMessageLogger.Services.Behaviors.MessageLogSvcRef;
using Direction = Conducido.WcfMessageLogger.Services.Behaviors.Entities.Direction;

namespace Conducido.WcfMessageLogger.Services.Behaviors.Logger
{
    /// <summary>
    /// Wrapper for logging the message
    /// </summary>
    public class LogMessage
    {
        private OperationContext _operationContext;
        private MessageLogConfiguration _messageLogConfiguration;

        /// <summary>
        /// Provide the operationcontext so this task can run async
        /// </summary>
        /// <param name="wcfOperationContext"></param>
        /// <param name=""></param>
        public LogMessage(OperationContext wcfOperationContext, MessageLogConfiguration messageLogConfiguration)
        {
            _operationContext = wcfOperationContext;
            _messageLogConfiguration = messageLogConfiguration;
        }

        /// <summary>
        /// Logs the message to the datastore
        /// This can be routed to the database or via msmq or someting else if desired to another dedicated logservice
        /// </summary>
        /// <param name="timestamp"></param>
        /// <param name="message"></param>
        /// <param name="direction"></param>
        /// <param name="messageId"></param>
        /// <param name="correlationId"></param>
        /// <param name="isClientCall"></param>
        public void Log(DateTime timestamp, string message, Direction direction, Guid messageId, Guid correlationId,
                        bool isClientCall)
        {
            if (_messageLogConfiguration.WayOfLogging == MessageLogType.Database)
            {
                LogToDatabase(timestamp, message, direction, messageId, correlationId, isClientCall);
            }
            else
            {
                LogToService(timestamp, message, direction, messageId, correlationId, isClientCall);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="timestamp"></param>
        /// <param name="message"></param>
        /// <param name="direction"></param>
        /// <param name="messageId"></param>
        /// <param name="correlationId"></param>
        /// <param name="isClientCall"></param>
        private void LogToDatabase(DateTime timestamp, string message, Direction direction, Guid messageId,
                                   Guid correlationId, bool isClientCall)
        {
            using (MessagelogEntities context = CreateDbContext())
            {
                var log = new Log();
                log.Service = GetServiceName();
                log.Operation = GetOperation();
                log.Timestamp = timestamp;
                log.Message = message;
                log.Direction = direction.ToString();
                log.MessageId = messageId;
                log.CorrelationId = correlationId;
                log.ProcessId = GetProcessId();
                log.Process = GetProcessName();
                log.Servername = GetServerName();
                log.Url = GetUrl(isClientCall); //TODO: Create clientcall bool
                context.Log.AddObject(log);
                context.SaveChanges();
            }
        }

        private string GetServiceName()
        {
            OperationContext context = _operationContext;
            if (context != null)
            {
                return context.Host.Description.ServiceType.ToString();
            }
            return string.Empty;
        }

        private string GetUrl(bool isClientCall)
        {
            
            if (_operationContext != null &&
                _operationContext.Host.BaseAddresses.Count > 0)
            {
                if (!isClientCall)
                {
                    return _operationContext.Host.BaseAddresses[0].AbsoluteUri;
                }
                return string.Format(CultureInfo.InvariantCulture, "{0}{1}",
                                     "CALL_FROM:", _operationContext.Host.BaseAddresses[0].AbsoluteUri);
                    //TODO: TargetUrl?
            }

            return "URLNotFound";
        }

        private string GetOperation()
        {
            OperationContext wcfContext = _operationContext;
            if (!string.IsNullOrEmpty(wcfContext.IncomingMessageHeaders.Action))
            {
                string soapAction = wcfContext.IncomingMessageHeaders.Action;
                int counter = soapAction.LastIndexOf('/');
                string operationName = soapAction.Substring(counter + 1);

                return operationName;
            }
            return string.Empty;
        }

        /// <summary>
        /// Current machine
        /// </summary>
        /// <returns></returns>
        private string GetServerName()
        {
            return Environment.MachineName;
        }

        /// <summary>
        /// Return the id for the current process
        /// </summary>
        /// <returns></returns>
        private int GetProcessId()
        {
            Process currentProcess = Process.GetCurrentProcess();
            return currentProcess.Id;
        }

        private string GetProcessName()
        {
            Process currentProcess = Process.GetCurrentProcess();
            return currentProcess.ProcessName;
        }

        private void LogToService(DateTime timestamp, string message, Direction direction, Guid messageId,
                                  Guid correlationId, bool isClientCall)
        {
            var client = LogServiceClientFactory();
            try
            {
                var log = new MessageToLog();
                log.Service = GetServiceName();
                log.Operation = GetOperation();
                log.Timestamp = timestamp;
                XElement messageElement = XElement.Parse(message, LoadOptions.PreserveWhitespace);
                log.Message = messageElement;
                log.MessageDirection =
                    (MessageLogSvcRef.Direction) Enum.Parse(typeof (MessageLogSvcRef.Direction), direction.ToString());
                log.MessageId = messageId;
                log.CorrelationId = correlationId;
                log.ProcessId = GetProcessId();
                log.Process = GetProcessName();
                log.Servername = GetServerName();
                log.Url = GetUrl(isClientCall);
                client.LogMessage(log);
                client.Close();
            }
            catch(Exception)
            {
                client.Abort();
                throw;
            }
        }

        /// <summary>
        /// Creates <see cref="MessageLogClient"/> and inserts default configuration
        /// </summary>
        /// <returns></returns>
        private LogServiceClient LogServiceClientFactory()
        {
            var client = new LogServiceClient();

            if (_messageLogConfiguration.ServiceMessageLogTransport == ServiceMessageLogType.HTTP)
            {
                client.Endpoint.Binding = new BasicHttpBinding(BasicHttpSecurityMode.None);
                var binding = (BasicHttpBinding)client.Endpoint.Binding;
                binding.MaxReceivedMessageSize = 9999999;
                client.Endpoint.Address = new EndpointAddress(_messageLogConfiguration.Uri);
            }
            else
            {
                client.Endpoint.Binding = new NetMsmqBinding(NetMsmqSecurityMode.None);
                var binding = (NetMsmqBinding)client.Endpoint.Binding;
                binding.MaxReceivedMessageSize = 9999999;
                client.Endpoint.Address = new EndpointAddress(_messageLogConfiguration.Uri);
            }
            
            return client;
        }

        private MessagelogEntities CreateDbContext()
        {
            var context = new MessagelogEntities(); //TODO: Simplify connectionstring??
            return context;
        }
    }
}